package com.ysd.lis.aspect;

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ysd.lis.annotation.LogDetail;
import com.ysd.lis.entity.SysOrganization;
import com.ysd.lis.entity.SysUser;
import com.ysd.lis.mapper.sys.SysEventLogMapper;
import com.ysd.lis.mapper.sys.SysExceptionalLogMapper;
import com.ysd.lis.mapper.sys.SysOrganizationMapper;
import com.ysd.lis.service.SysUserService;
import com.ysd.util.RedisUserManager;
import com.ysd.util.ToolsUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.aspectj.lang.ProceedingJoinPoint;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import com.alibaba.fastjson.JSON;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponseWrapper;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @program: server_lis
 * @author: liuQi
 * @create: 2023-10-19 14:39
 */
@Order(1)
@Aspect
@Component
public class LogAspect {

    @Autowired
    SysEventLogMapper sysEventLogMapper;
    @Autowired
    SysUserService sysUserService;
    @Autowired
    SysOrganizationMapper sysOrganizationMapper;
    @Autowired
    SysExceptionalLogMapper exceptionalLogMapper;

    //为了记录方法的执行时间
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    @Pointcut("@annotation(com.ysd.lis.annotation.LogDetail)")
    public void operationLog() {
    }

    @Before("operationLog()")
    public void beforMethod(JoinPoint point) {
        startTime.set(System.currentTimeMillis());
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    @Pointcut("execution(* *..controller..*.*(..))")
    public void operExceptionLogPoinCut() {
    }

//    @Pointcut("execution(* com.ysd.lis.controller.*.*(..))")
//    public void operationLog() {
//    }

//    @Around("operationLog()")
//    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//        SysEventLog sysEventLogEntry = new SysEventLog();
//        Object res = null;
//        long time = System.currentTimeMillis();
//
//
//        try {
//            res = joinPoint.proceed();
//            sysEventLogEntry.setOperateTime(Timestamp.valueOf(LocalDateTime.now())); //服务结束时间
//            time = System.currentTimeMillis() - time;
//            // sysEventLogEntry.setTakeTime(time+"");
//            return res;
//        } finally {
//            try {
//                JSONObject jsonObject = JSONUtil.parseObj(res);
//                JSONObject status = JSONUtil.parseObj(jsonObject.get("status"));
//                String code = status.get("code") + "";
//                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//                Method method = signature.getMethod();
//                Object[] args = joinPoint.getArgs();
//                //方法执行完成后增加日志
//                LogDetail logDetail = signature.getMethod().getAnnotation(LogDetail.class);
//                OperationType operationType = OperationType.OTHER;
//                if (logDetail != null) {
//                    operationType = logDetail.operationType() != null ? logDetail.operationType() : OperationType.OTHER;
//                }
//                sysEventLogEntry.setOperationType(operationType); //事件
//                sysEventLogEntry.setModule(logDetail.module());//模块
//                String id = logDetail.requestParam();//获取注解定义的名称
//                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//                HttpServletRequest request = requestAttributes.getRequest();
//                Map<String, Object> map =  ComputerInfo.getComputerInfo(request);
//                String ip = (String) map.get("ip");
//                String name = (String)map.get("name");
//                sysEventLogEntry.setMachineIp(ip);
//                sysEventLogEntry.setMachineName(name);
//                SysUser sysUser = RedisUserManager.getUser();
//                if (ToolsUtils.isNotEmpty(sysUser)) {
//                    sysEventLogEntry.setUserName(sysUser.getUsername()); //登陆人账号
//                    sysEventLogEntry.setUserId(sysUser.getId()); //登陆人ID
//                    sysEventLogEntry.setOrgId(sysUser.getOrgId());
//                    SysOrganization sysOrganization = sysOrganizationMapper.selectById(sysUser.getOrgId());
//                    if (ToolsUtils.isNotEmpty(sysOrganization)) {
//                        if (ToolsUtils.isNotEmpty(sysOrganization.getName())) {
//                            sysEventLogEntry.setHospitalName(sysOrganization.getName());
//                        }
//                        if (ToolsUtils.isNotEmpty(sysOrganization.getCode())) {
//                            sysEventLogEntry.setHospitalCode(sysOrganization.getCode());
//                        }
//
//                    }
//
//                }
//
//                if (logDetail.operationType().equals(OperationType.LOGON)) {
//                    sysEventLogEntry.setReqParam("");
//                    sysEventLogEntry.setRepParam("");
//                } else {
//                    sysEventLogEntry.setReqParam(JSONUtil.toJsonStr(args));
//                    sysEventLogEntry.setRepParam(JSONUtil.toJsonStr(res));
//                }
//                sysEventLogEntry.setServiceName(id);
//                sysEventLogEntry.setOperateTime(Timestamp.valueOf(LocalDateTime.now())); //当前操作时间
//                if (code.equals("1")) {
//                    sysEventLogEntry.setFlag("1");
//                } else {
//                    sysEventLogEntry.setFlag("0");
//                }
//                LocalVariableTableParameterNameDiscoverer localVariableTable = new LocalVariableTableParameterNameDiscoverer();
//                String[] paraNameArr = localVariableTable.getParameterNames(method);
//                ExpressionParser parser = new SpelExpressionParser();
//                StandardEvaluationContext context = new StandardEvaluationContext();
//                for (int i = 0; i < paraNameArr.length; i++) {
//                    context.setVariable(paraNameArr[i], args[i]);
//                }
//                if (id.matches("^#.*.$")) {
//                    id = parser.parseExpression(id).getValue(context, String.class); //EL获取ID值
//                }
//                sysEventLogEntry.setOperatorId(id);
//                sysEventLogMapper.insert(sysEventLogEntry);
//            } catch (Exception e) {
//                //处理错误异常信息
//
//                //SysExceptionalLog
//
//
//                System.out.println("LogAspect 操作失败------------------：" + e.getMessage());
//                e.printStackTrace();
//            }
//        }
//    }


    /**
     * 正常返回通知，拦截用户操作日志，连接点正常执行完成后执行， 如果连接点抛出异常，则不会执行
     *
     * @param joinPoint 切入点
     * @param result    返回结果
     * @author: Bai
     */
    @AfterReturning(value = "operationLog()", returning = "result")
    public void saveOperLog(JoinPoint joinPoint, Object result) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        SysEventLog sysEventLogEntry = new SysEventLog();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();

            // 解析切面的所有参数，放入一个Map
            Map<String, Object> paramsMap = produceParamsMap(joinPoint, signature);
            // 获取操作
            LogDetail logDetail = method.getAnnotation(LogDetail.class);

            OperationType operationType = OperationType.OTHER;

            if (null != logDetail) {
                sysEventLogEntry.setModule(logDetail.module());//设置模块名称


                sysEventLogEntry.setLogLevel(logDetail.logLevel());//日志级别
                if (StringUtils.isNotBlank(logDetail.businessName())) {
                    // sysEventLogEntry.setBusinessName(paramsMap.get(logDetail.businessName()).toString());
                    sysEventLogEntry.setBusinessName(logDetail.businessName());
                }
                operationType = logDetail.operationType() != null ? logDetail.operationType() : OperationType.OTHER;
            }
            sysEventLogEntry.setOperationType(operationType);//事
            // 将入参转换成json
            Timestamp operate = Timestamp.valueOf(LocalDateTime.now());
            String params = argsArrayToString(joinPoint.getArgs());
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName + "()";
            sysEventLogEntry.setMethod(methodName); //设置请求方法
            sysEventLogEntry.setRequestMethod(request.getMethod());//设置请求方式
            sysEventLogEntry.setReqParam(params); // 请求参数
            sysEventLogEntry.setRepParam(JSON.toJSONString(result)); // 返回结果

            sysEventLogEntry.setMachineIp(getIp(request)); // IP地址
            sysEventLogEntry.setMachineName(getMachineName()); //计算机名称
            // operlog.setIpLocation("湖北武汉"); // IP归属地（真是环境中可以调用第三方API根据IP地址，查询归属地）
            sysEventLogEntry.setRequestUrl(request.getRequestURI()); // 请求URI
            sysEventLogEntry.setOperateTime(operate); // 时间
            sysEventLogEntry.setFlag("0");//操作状态（0正常 1异常）
            Long takeTime = System.currentTimeMillis() - startTime.get();//记录方法执行耗时时间（单位：毫秒）
            sysEventLogEntry.setTakeTime(takeTime + "");
            SysUser sysUser = new SysUser();
            if (operationType.equals(OperationType.LOGON)) {//登录处理登录日志信息的
                JSONObject json = JSON.parseObject(params);
                String username = json.getString("username");
                String password = json.getString("password");
                String passwords = SecureUtil.md5(password);
                sysUser = sysUserService.getPassword(username);

            } else {
                sysUser = RedisUserManager.getUser();
            }
            if (ToolsUtils.isNotEmpty(sysUser)) {
                sysEventLogEntry.setUserName(sysUser.getUsername()); //登陆人账号
                sysEventLogEntry.setUserId(sysUser.getId()); //登陆人ID
                sysEventLogEntry.setOrgId(sysUser.getOrgId());
                SysOrganization sysOrganization = sysOrganizationMapper.selectById(sysUser.getOrgId());
                if (ToolsUtils.isNotEmpty(sysOrganization)) {
                    if (ToolsUtils.isNotEmpty(sysOrganization.getName())) {
                        sysEventLogEntry.setHospitalName(sysOrganization.getName());
                    }
                    if (ToolsUtils.isNotEmpty(sysOrganization.getCode())) {
                        sysEventLogEntry.setHospitalCode(sysOrganization.getCode());
                    }

                }

            }
            String logContent = dynamicParam(logDetail.content(), paramsMap);
            sysEventLogEntry.setContent(logContent);//设置日志内容
            if (operationType.equals(OperationType.LOGONOUT)) {
                //销毁redis中的缓存信息
                RedisUserManager.destroyUser();
            }
            //插入数据库
            sysEventLogMapper.insert(sysEventLogEntry);
        } catch (Exception e) { //插入数据库
            sysEventLogMapper.insert(sysEventLogEntry);
            e.printStackTrace();
        } catch (Throwable e2) {
            sysEventLogMapper.insert(sysEventLogEntry);
            e2.printStackTrace();
        }
    }

    /**
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     */
    @AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")
    public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {

        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        SysExceptionalLog exceptionalLog = new SysExceptionalLog();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName + "()";
            // 解析切面的所有参数，放入一个Map
            Map<String, Object> paramsMap = produceParamsMap(joinPoint, signature);
            // 获取操作
            LogDetail logDetail = method.getAnnotation(LogDetail.class);
            OperationType operationType = OperationType.OTHER;
            if (null != logDetail) {
                exceptionalLog.setModule(logDetail.module());//设置模块名称
                String logContent = dynamicParam(logDetail.content(), paramsMap);
                exceptionalLog.setContent(logContent);//设置日志内容
                exceptionalLog.setLogLevel(logDetail.logLevel());//日志级别
                operationType = logDetail.operationType() != null ? logDetail.operationType() : OperationType.OTHER;
            }
            exceptionalLog.setOperationType(operationType);//事件
            // 将入参转换成json
            String params = argsArrayToString(joinPoint.getArgs());
            exceptionalLog.setMethod(methodName); //设置请求方法
            exceptionalLog.setRequestMethod(request.getMethod());//设置请求方式
            exceptionalLog.setReqParam(params); // 请求参数
            exceptionalLog.setMachineIp(getIp(request)); // IP地址
            exceptionalLog.setMachineName(getMachineName());
            //operlog.setIpLocation("浙江杭州"); // IP归属地（真是环境中可以调用第三方API根据IP地址，查询归属地）
            exceptionalLog.setRequestUrl(request.getRequestURI()); // 请求URI
            exceptionalLog.setOperateTime(Timestamp.valueOf(LocalDateTime.now())); // 时间
            exceptionalLog.setFlag("1");//操作状态（0正常 1异常）
            exceptionalLog.setErrorMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));//记录异常信息

            //插入数据库
            SysUser sysUser = RedisUserManager.getUser();
            if (ToolsUtils.isNotEmpty(sysUser)) {
                exceptionalLog.setUserName(sysUser.getUsername()); //登陆人账号
                exceptionalLog.setUserId(sysUser.getId()); //登陆人ID
                exceptionalLog.setOrgId(sysUser.getOrgId());
                SysOrganization sysOrganization = sysOrganizationMapper.selectById(sysUser.getOrgId());
                if (ToolsUtils.isNotEmpty(sysOrganization)) {
                    if (ToolsUtils.isNotEmpty(sysOrganization.getName())) {
                        exceptionalLog.setHospitalName(sysOrganization.getName());
                    }
                    if (ToolsUtils.isNotEmpty(sysOrganization.getCode())) {
                        exceptionalLog.setHospitalCode(sysOrganization.getCode());
                    }

                }
            }
            exceptionalLogMapper.insert(exceptionalLog);
        } catch (Exception e2) {
            //对于redis异常捕捉
            exceptionalLogMapper.insert(exceptionalLog);
            e2.printStackTrace();
        } catch (Throwable e3) {
            //对入参异常捕捉
            exceptionalLogMapper.insert(exceptionalLog);
        }
    }

    /**
     * 把切面中的各种不同类型的参数信息，统一放入一个Map，从而不需要再去别的变量里面寻找参数
     */
    private Map<String, Object> produceParamsMap(JoinPoint joinPoint, MethodSignature signature) {
        Map<String, Object> paramsMap = new HashMap<>(); // 包含此切面的所有参数值的Map
        String[] argNames = signature.getParameterNames(); // 参数名
        Object[] argObjects = joinPoint.getArgs(); // 参数
        List<String> argNameList = Arrays.asList(argNames);
        for (int i = 0; i < argNameList.size(); i++) {
            Class<?> clazz = argObjects[i].getClass();
            // 把简单数据类型的参数放入参数Map （包括了Request对象中的ParameterMap）
            Object currentParam = joinPoint.getArgs()[i];
            if (isSimpleType(clazz.getName()) || argObjects[i] instanceof List) {
                paramsMap.put(argNameList.get(i), currentParam);
                continue;
            }
            if (argNameList.get(i).equals("file")||argNameList.get(i).equals("response")){
                paramsMap.put(argNameList.get(i), "受限的支持类型");
                continue;
            }
            // 把Request对象中的Attribute属性放入参数Map
            if (currentParam instanceof ServletRequest) {
                ServletRequest request = (ServletRequest) currentParam;
                Enumeration<String> attr = request.getAttributeNames();
                while (attr.hasMoreElements()) {
                    String attrName = attr.nextElement();
                    String attrValue = "";
                    if (request.getAttribute(attrName) != null) {
                        attrValue = request.getAttribute(attrName).toString();
                    }
                    paramsMap.put(attrName, attrValue);
                }
                continue;
            }

            // 把不属于以上两种类型的参数（即Spring MVC/Spring Boot自动接收的Bean类型）放入参数Map
            JSONObject object = (JSONObject) JSONObject.toJSON(currentParam);

            object.keySet().stream().forEach(p -> {
                paramsMap.put(p, object.get(p));
            });
        }
        return paramsMap;
    }

    /**
     * 是不是简单数据类型（简单类型无需使用序列化框架，直接取值即可）
     */
    private static boolean isSimpleType(String name) {
        AtomicBoolean result = new AtomicBoolean(false);
        String[] className = {"String", "Integer", "Long", "int", "float", "double", "char"};
        Arrays.asList(className).forEach(n -> {
            if (name.contains(n)) {
                result.set(name.contains(n));
            }
        });
        return result.get();
    }

    /**
     * 处理注解中的动态参数，把变量占位符替换成实际的变量值
     */
    private static String dynamicParam(String logContent, Map<String, Object> paramsMap) {
        AtomicReference<String> actual = new AtomicReference<>(logContent);
        if (StringUtils.isBlank(logContent)) {
            return actual.get();
        }
        paramsMap.entrySet().stream().forEach(p -> {
            if (actual.get().contains(p.getKey())) {
                actual.set(actual.get().replaceAll(p.getKey(), p.getValue() + ""));
            }
        });
        return actual.get();
    }

    /**
     * 转换异常信息为字符串
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        message = substring(message, 0, 2000);
        return message;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) throws Throwable {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (o != null) {
                    try {
                        // 检测是否为 MultipartFile 类型
                        if (o instanceof MultipartFile || o instanceof StandardMultipartHttpServletRequest|| o instanceof CTWorkbook|| o instanceof HttpServletResponseWrapper) {
                            //params += "MultipartFile: " + ((MultipartFile) o).getOriginalFilename() + " ";
                            continue;
                        }
                        Object jsonObj = JSON.toJSON(o);
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return params.trim();
    }


    //字符串截取
    public static String substring(String str, int start, int end) {
        if (str == null) {
            return null;
        } else {
            if (end < 0) {
                end += str.length();
            }

            if (start < 0) {
                start += str.length();
            }

            if (end > str.length()) {
                end = str.length();
            }

            if (start > end) {
                return "";
            } else {
                if (start < 0) {
                    start = 0;
                }

                if (end < 0) {
                    end = 0;
                }
                return str.substring(start, end);
            }
        }
    }

    /**
     * 转换request 请求参数
     *
     * @param paramMap request获取的参数数组
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> returnMap = new HashMap<>();
        for (String key : paramMap.keySet()) {
            returnMap.put(key, paramMap.get(key)[0]);
        }
        return returnMap;
    }

    //根据HttpServletRequest获取访问者的IP地址
    public static String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    public static String getMachineName() {
        try {
            // 获取本地主机地址
            InetAddress localHost = InetAddress.getLocalHost();
            // 获取主机名
            String hostName = localHost.getHostName();
            return hostName;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
